#include <cnix/string.h>
#include <cnix/errno.h>
#include <cnix/devices.h>
#include <cnix/fs.h>
#include <cnix/kernel.h>

int do_access(const char * pathname, int expect)
{
	mode_t mode;
	uid_t uid;
	gid_t gid;
	int error;
	struct inode * inoptr;

	checkname(pathname);

	inoptr = namei(pathname, &error, 0);
	if(!inoptr)
		return error;

	error = 0;

	if(expect == F_OK) // file exists
		goto out;

	mode = inoptr->i_ops->mode(inoptr);
	uid = current->uid;
	gid = current->gid;

	if(uid == SU_UID){ // super user
		// expect executable, but not a executable file
		if((expect & 1) && !(mode & (1 + 8 + 64)))
			error = -EACCES;

		goto out;
	}

	// owner
	if((uid == inoptr->i_ops->getuid(inoptr)) && ((expect << 6) & mode))
		goto out;

	// same group
	if((gid == inoptr->i_ops->getgid(inoptr)) && ((expect << 3) & mode))
		goto out;

	// other user
	if(expect & mode)
		goto out;

	error = -EACCES;

out:
	iput(inoptr);

	return error;
}

int do_chmod(const char * path, mode_t mode)
{
	int error;
	struct inode * inoptr;

	checkname(path);

	inoptr = namei(path, &error, 0);
	if(!inoptr)
		return error;

	if((current->euid != SU_UID)
		&& (current->euid != inoptr->i_ops->getuid(inoptr))){
		iput(inoptr);
		return -EACCES;
	}

	inoptr->i_dirty = 1;

	inoptr->i_ops->setmode(
		inoptr,
		inoptr->i_ops->mode(inoptr) | (mode & I_UGRWX)
		);
	inoptr->i_update |= CTIME;

	iput(inoptr);

	return 0;
}

int do_chown(const char * path, uid_t owner, gid_t group)
{
	int error;
	uid_t uid;
	gid_t gid;
	struct inode * inoptr;

	checkname(path);

	inoptr = namei(path, &error, 0);
	if(!inoptr)
		return error;

	uid = inoptr->i_ops->getuid(inoptr);
	gid = inoptr->i_ops->getgid(inoptr);

	if(current->euid == SU_UID)
		inoptr->i_ops->setuid(inoptr, owner);
	else if((current->euid != uid) && (uid != owner)){
		iput(inoptr);
		return -EACCES;
	}else if(current->egid != group){
		iput(inoptr);
		return -EACCES;
	}

	inoptr->i_dirty = 1;

	if(group > 0)
		inoptr->i_ops->setgid(inoptr, group);

	inoptr->i_update |= CTIME;

	iput(inoptr);

	return error;
}

static BOOLEAN can_write(struct inode *inoptr)
{
	uid_t uid = current->uid;
	gid_t gid = current->gid;
	mode_t mode;

	mode = inoptr->i_ops->mode(inoptr);
	
	/* owner */
	if ((uid == inoptr->i_ops->getuid(inoptr)) && ((I_WB << 6) & mode))
		return TRUE;

	/* same group */
	if ((gid == inoptr->i_ops->getgid(inoptr)) && ((I_WB << 3) & mode))
		return TRUE;

	/* other user */
	if (I_WB & mode)
		return TRUE;

	return FALSE;
}

int do_utime(const char *path, const struct utimbuf *buf)
{
	int error = -EACCES;
	struct inode * inoptr;

	checkname(path);

	inoptr = namei(path, &error, 0);
	if(!inoptr)
		return error;

	if (current->euid != SU_UID
		&& current->euid != inoptr->i_ops->getuid(inoptr))
	{
		if (buf || !can_write(inoptr))
		{
			iput(inoptr);
			return -EACCES;
		}
	}

	error = 0;
	
	if (buf)
	{
		struct utimbuf buf1;

		if(memcpy_from_user(&buf1, buf, sizeof(struct utimbuf)) < 0){
			iput(inoptr);
			return -EFAULT;
		}

		inoptr->i_ops->update(inoptr, buf1.actime, buf1.modtime, 0);
	}
	else
	{
		inoptr->i_update |= (ATIME | MTIME);
	}
	
	inoptr->i_update |= CTIME;
	inoptr->i_dirty = 1;

	iput(inoptr);

	return error;
}
